# 06 常用的重要特征Trait ## 6.1 拷贝控制和移动语义: Clone VS Copy 在Rust中将一个变量赋值给另外一个变量,或者传入一个参数, 其行为和变量是否实现Copy特征有关系; 如果数据没有实现Copy特征Trait,数据的所有权将被转移到新的变量或者到函数入参之中,原来的变量将失效; 如果数据实现了Copy特征Trait,数据的内容将完全根据底层字节memroy copy拷贝到新的变量或者函数临时入参之中,原来的变量继续有效; 常见的基础数据: 整数,浮点,布尔已经内置实现了Copy特征; Copy特征继承于Clone特征,如果要实现Copy特征,就必须要实现Clone特征; Copy特征没有需要实现的Trait函数,Clone只有一个clone函数需要实现; 在实现Clone特征的clone函数的时候,是对数据进行拷贝产生新的对象,如果存在Copy继承, Clone应该保持逻辑上的一致性; 实现Clone和Copy的方式 ```rust // 方式一,通过派生宏,继承编译器默认提供的 #[derive(Clone, Copy)] struct Point { x: i32, y: i32, } // 注意: // 1. 如果继承默认的Copy,则要求所有成员都实现了Copy; // 2. 如果继承默认的Clone,则要求所有的成员都实现了Clone; // 3. 如果继承默认的Copy,当前类必须实现Clone特征(无论是继承还是自定义实现); // 方式二: 自定义clone函数实现Clone特征 #[derive(Debug)] struct UserSession { username: String, // 需要被克隆的字段 id: u64, // 需要被克隆的字段 login_token: String, // 假设这个字段在克隆时不需要复制,直接设为空或重新生成 } // 手动实现 Clone impl Clone for UserSession { fn clone(&self) -> Self { UserSession { // 正常的字段调用 .clone() username: self.username.clone(), id: self.id, // u64 实现了 Copy,可以直接赋值 // 定制化逻辑:克隆时故意不复制原样,而是赋一个新值或默认值 login_token: String::from("new_empty_token"), } } } ``` ## 6.2 引用自动转换机制: Deref Coercion(解引用强制转换) 当数据的引用 `&T`, 可以自动转为另外一种的引用 `&X` 的机制,被称为`Deref Coercion(解引用强制转换)`; 常用于一些数据类型相关联的自动兼容和适配; 比如 `&String` 能勾自动解引用为 `&str`; 当T实现了Deref特征之后,`Option.as_deref()`会自动解析为`Option<&targetT>`; 下面的代码是Rust内部关于Deref的定义, 是自身引用到另外一个引用的转换,转换目标需要自己定义, 过程一般是内部已有数据的引用: ```rust pub const trait Deref: PointeeSized { /// The resulting type after dereferencing. #[stable(feature = "rust1", since = "1.0.0")] #[rustc_diagnostic_item = "deref_target"] #[lang = "deref_target"] type Target: ?Sized; /// Dereferences the value. #[must_use] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_diagnostic_item = "deref_method"] fn deref(&self) -> &Self::Target; } ``` ```{note} 在Rust的接口抽象中Trait中定义的类型 `type Target: ?Sized;`表示长度不确定,需要实现的时候定义; ``` ## 模式匹配和所有权转移 在 Rust 中,无论是使用 `match` 还是 `if let`,**模式匹配是否转移所有权,完全取决于解构出来的数据类型是否实现了 `Copy**`。 --- ### 1. 触发所有权转移(Move)的场景 当被匹配的变量**未实现 `Copy**`(如 `String`、`Vec` 或自定义结构体),且你在模式匹配中直接解构出了内部变量名,就会将所有权移动(Move)到该内部变量中,导致原变量失效。 ```rust let opt_str: Option = Some(String::from("Rust")); // 发生所有权转移:String 的所有权被移交给了 inner match opt_str { Some(inner) => println!("inner 拿走了所有权: {}", inner), None => (), } // ❌ 编译报错!opt_str 已经被移动,不再有效 // println!("{:?}", opt_str); ``` --- ### 2. 避免转移所有权:只匹配引用的 3 种方法 #### 方法一:对匹配表达式直接取引用 `&`(最常用、最推荐) 直接在 `match` 或 `if let` 的目标表达式前加 `&`。这样匹配出的内部变量会自动推导为引用类型(如 `&String`),完全不破坏原变量的所有权。 * **`match` 写法:** ```rust let opt_str: Option = Some(String::from("Rust")); // 对 &opt_str 进行匹配,inner 类型自动推导为 &String match &opt_str { Some(inner) => println!("借用查看: {}", inner), None => (), } // ✅ 原变量完全有效 println!("原变量依然可用: {:?}", opt_str); ``` * **`if let` 写法:** ```rust let opt_str: Option = Some(String::from("Rust")); if let Some(inner) = &opt_str { println!("借用查看: {}", inner); } // ✅ 原变量完全有效 println!("原变量依然可用: {:?}", opt_str); ``` --- #### 方法二:使用 `.as_ref()` 方法(链式调用时极佳) `.as_ref()` 是标准库为 `Option` 和 `Result` 提供的专属方法。它会返回一个新的包装引用容器(例如将 `Option` 转为 `Option<&T>`)。 ```rust let opt_str: Option = Some(String::from("Rust")); // opt_str.as_ref() 生成了 Option<&String> if let Some(inner) = opt_str.as_ref() { println!("借用查看: {}", inner); } // ✅ 原变量完全有效 println!("原变量依然可用: {:?}", opt_str); ``` --- #### 方法三:在模式内部使用 `ref` 关键字(经典语法) 如果不修饰外部表达式,你可以在模式内部解构变量的前面加上 `ref` 关键字,显式告诉编译器“我只需要这个字段的不可变引用”。 ```rust let opt_str: Option = Some(String::from("Rust")); match opt_str { // 明确要求 inner 是 &String 引用 Some(ref inner) => println!("借用查看: {}", inner), None => (), } // ✅ 原变量完全有效 println!("原变量依然可用: {:?}", opt_str); ``` --- ### 3. 特殊延伸:如果你需要修改内部数据(可变引用) 当你不仅要避免所有权转移,还需要**修改**内部的数据时,对应的方法非常简单——只需将“不可变引用”切换为“可变引用”: | 需求 | 表达式取引用 | 标准库转换方法 | 模式关键字 | | --- | --- | --- | --- | | **只读(不可变引用)** | `match &var` 或 `if let ... = &var` | `var.as_ref()` | `Some(ref inner)` | | **修改(可变引用)** | `match &mut var` 或 `if let ... = &mut var` | `var.as_mut()` | `Some(ref mut inner)` | * **修改可变引用示例:** ```rust let mut opt_str: Option = Some(String::from("hello")); // 使用 as_mut() 拿到 Option<&mut String> if let Some(inner) = opt_str.as_mut() { inner.push_str(", world!"); // 直接修改底层字符串 } // ✅ 原变量已被成功修改,输出: Some("hello, world!") println!("{:?}", opt_str); ``` --- ### 总结口诀 1. **基本类型**(如 `usize`、`i32`、`char`):已实现 `Copy`,怎么写都不移走所有权。 2. **复合类型**(如 `String`、`Vec`、自定义 `struct`): * 直接匹配 $\rightarrow$ **所有权转移(Move)** * 加 `&` / `.as_ref()` / `ref` $\rightarrow$ **借用引用(Borrow)**